﻿// ****************************************************************************
// <copyright file="WeakAction.cs" company="GalaSoft Laurent Bugnion">
// Copyright © GalaSoft Laurent Bugnion 2009-2013
// </copyright>
// ****************************************************************************
// <author>Laurent Bugnion</author>
// <email>laurent@galasoft.ch</email>
// <date>18.9.2009</date>
// <project>GalaSoft.MvvmLight</project->
// <web>http://www.galasoft.ch</web>
// <license>
// See license.txt in this solution or http://www.galasoft.ch/license_MIT.txt
// </license>
// <LastBaseLevel>BL0015</LastBaseLevel>
// ****************************************************************************
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace TimersXP.Helpers
{
    /// <summary>
    /// Stores an <see cref="Action"/> without causing a hard reference to be created to the Action's owner.
    /// The owner can be garbage collected at any time.
    /// </summary>
    ////[ClassInfo(typeof(WeakAction),
    ////    VersionString = "4.2.16",
    ////    DateString = "201309262235",
    ////    Description = "A class allowing to store and invoke actions without keeping a hard reference to the action's target.",
    ////    UrlContacts = "http://www.galasoft.ch/contact_en.html",
    ////    Email = "laurent@galasoft.ch")]
    public class WeakAction
    {
        #region Private Class Members
#if SILVERLIGHT
        /// <summary>The action.</summary>
        private Action action;
#endif
        /// <summary>The static action</summary>
        private Action staticAction;
        #endregion Private Class Members

        #region Class Properties
        /// <summary>Gets or sets the <see cref="MethodInfo"/> corresponding to this WeakAction's method passed in the constructor.</summary>
        /// <value>The method.</value>
        protected MethodInfo Method { get; set; }

        /// <summary>Gets the name of the method that this WeakAction represents.</summary>
        /// <value>The name of the method.</value>
        public virtual string MethodName
        {
            get
            {
                if (this.staticAction != null)
                {
#if NETFX_CORE
                    return this.staticAction.GetMethodInfo().Name;
#else
                    return this.staticAction.Method.Name;
#endif
                }
#if SILVERLIGHT
                if (this.action != null)
                    return this.action.Method.Name;

                if (Method != null)
                    return Method.Name;

                return string.Empty;
#else
                return Method.Name;
#endif
            }
        }

        /// <summary>
        /// Gets or sets a WeakReference to this WeakAction's action's target.
        /// This is not necessarily the same as <see cref="Reference"/>, for example if the method is anonymous.
        /// </summary>
        /// <value>The action reference.</value>
        protected WeakReference ActionReference { get; set; }

        /// <summary>
        /// Gets or sets a WeakReference to the target passed when constructing the WeakAction.
        /// This is not necessarily the same as <see cref="ActionReference" />, for example if the method is anonymous
        /// </summary>
        /// <value>The reference.</value>
        protected WeakReference Reference { get; set; }

        /// <summary>Gets a value indicating whether the WeakAction [is static] or not.</summary>
        /// <value><c>true</c> if [is static]; otherwise, <c>false</c>.</value>
        public bool IsStatic
        {
            get
            {
#if SILVERLIGHT
                return (this.action != null && this.action.Target == null)
                    || this.staticAction != null;
#else
                return this.staticAction != null;
#endif
            }
        }

        /// <summary>Gets a value indicating whether the Action's owner is still alive, or if it was collected by the Garabage Collector already.</summary>
        /// <value><c>true</c> if [is alive]; otherwise, <c>false</c>.</value>
        public virtual bool IsAlive
        {
            get
            {
                if (this.staticAction == null && Reference == null)
                    return false;

                if (this.staticAction != null)
                {
                    if (Reference != null)
                        return Reference.IsAlive;
                    return true;
                }
                return Reference.IsAlive;
            }
        }

        /// <summary>Gets the Action's owner. This object is stored as a <see cref="WeakReference"/></summary>
        /// <value>The target.</value>
        public object Target
        {
            get
            {
                if (this.Reference == null)
                    return null;
                return this.Reference.Target;
            }
        }

        /// <summary>The target of the weak reference.</summary>
        /// <value>The action target.</value>
        protected object ActionTarget
        {
            get
            {
                if (ActionReference == null)
                    return null;
                return ActionReference.Target;
            }
        }
        #endregion Class Properties

        #region Constructors
        /// <summary>Initializes a new instance of the <see cref="WeakAction"/> class.</summary>
        protected WeakAction() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="WeakAction"/> class.
        /// </summary>
        /// <param name="action">The action.</param>
        public WeakAction(Action action) : this(action == null ? null : action.Target, action) { }

        /// <summary>Initializes a new instance of the <see cref="WeakAction"/> class.</summary>
        /// <param name="target">The action's ownder.</param>
        /// <param name="action">The action that will be associated to this instance.</param>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Method should fail with an exception if action is null.")]
        public WeakAction(object target, Action action)
        {
#if NETFX_CORE
            if (this.action.GetMethodInfo().IsStatic)
#else
            if (action.Method.IsStatic)
#endif
            {
                this.staticAction = action;
                if (target != null)
                {
                    // Keep a reference to the target to control the WeakAction's lifetime.
                    Reference = new WeakReference(target);
                }
                return;
            }

#if SILVERLIGHT
            if (!action.Method.IsPublic || (action.Target != null && !action.Target.GetType().IsPublic && !action.Target.GetType().IsNestedPublic))
                this.action = action;
            else
            {
                var name = action.Method.Name;
                if (name.Contains("<") && name.Contains(">"))
                {
                    //Anonymous method
                    this.action = action;
                }
                else
                {
                    Method = action.Method;
                    ActionReference = new WeakReference(action.Target);
                }
            }
#else
#if NETFX_CORE
            Method = action.GetMethodInfo();
#else
            Method = action.Method;
#endif
            ActionReference = new WeakReference(action.Target);
#endif
            Reference = new WeakReference(target);
        }
        #endregion Constructors

        #region Public Functions
        /// <summary>Executes the action. This only happens if the action's owner is still alive.</summary>
        public void Execute()
        {
            if (this.staticAction != null)
            {
                this.staticAction();
                return;
            }
            
            var actionTarget = ActionTarget;

            if (IsAlive)
            {
                if (Method != null && ActionReference != null && actionTarget != null)
                {
                    Method.Invoke(actionTarget, null);
                    return;
                }

#if SILVERLIGHT
                if (this.action != null)
                {
                    this.action();
                    return;
                }
#endif
            }
        }

        /// <summary>Sets the reference that this instance stores to null.</summary>
        public void MarkForDeletion()
        {
            Reference = null;
            ActionReference = null;
            Method = null;
            this.staticAction = null;

#if SILVERLIGHT
            this.action = null;
#endif
        }
        #endregion Public Functions
    }
}
